/**
 * 寄存器
 */
let regs = {
    eax: 1,
    ebx: 0xb7fc8000,
    ecx: 147483561,
    edx: -1073745052,
    /**
     * 栈底
     */
    ebp: globalConfig.initialEsp + 40,
    /**
     * 栈顶
     */
    esp: globalConfig.initialEsp,
}

/**
 * pc寄存器，由于pc寄存器的动画比较多，就不放在regs里了
 */
let pc = {
    val: 0x804844a,
    get() {
        return this.val
    },
    async set(val) {
        // 更新值
        this.val = val
        // 更新页面中寄存器的内容
        setMemoryVal('pc', val)
        let i = this.getIndex()
        // 显示提示文本
        if (asm[i].text) {
            setInfo(asm[i].text)
        }
        // 移动pc指针
        await movePointer('pc', this.val)

        return true
    },
    async next() {
        let i = this.getIndex()
        await this.set(asm[i + 1].add)
    },
    getIndex() {
        return +$(`.memory-${getHex(this.val)}`).dataset.i
    },
}

/**
 * 求数列辅助对象
 */
let fibonacci = {
    initial: globalConfig.an,
    paramStack: [],
    cmpl: undefined,
    push(v) {
        this.paramStack.push(v)
    },
    getCurr() {
        return this.paramStack.at(-1)
    },
}

/**
 * 汇编
 */
let asm = [
    {
        add: '<f>',
        code: '',
        asmCode: '',
    },
    {
        add: 0x80483f4,
        code: '55',
        asmCode: 'push   %ebp',
        anime: async () => {
            await Promise.all([pc.next(), pushStack(regs.ebp)])
        },
        text: 'push   %ebp 指令会把寄存器ebp的值压入栈中',
    },
    {
        add: 0x80483f5,
        code: '89 e5',
        asmCode: 'mov    %esp,%ebp',
        anime: async () => {
            // 把esp的值传送给ebp

            regs.ebp = regs.esp
            await Promise.all([
                pc.next(),
                setMemoryVal('ebp', regs.ebp),
                movePointer('ebp', regs.esp),
            ])
        },
        text: 'mov    %esp,%ebp 指令会把寄存器esp的值赋给寄存器ebp',
    },
    {
        add: 0x80483f7,
        code: '53',
        asmCode: 'push   %ebx',
        anime: async () => {
            await Promise.all([pc.next(), pushStack(regs.ebx)])
        },
        text: 'push   %ebx 指令会把寄存器ebx的值压入栈中',
    },
    {
        add: 0x80483f8,
        code: '83 ec 14',
        asmCode: 'sub    $0x14,%esp',
        anime: async () => {
            // 开辟20个字节栈空间
            await Promise.all([
                pc.next(),
                getStackSpace(5),
                cCodeAction.setPC(3),
                cCodeAction.pointerAnim({
                    width: 80,
                    left: 150,
                }),
            ])
        },
        text: 'sub    $0x14,%esp 指令会开辟20个字节的栈空间',
    },
    {
        add: 0x80483fb,
        code: '83 7d 08 00',
        asmCode: 'cmpl   $0x0,0x8(%ebp)',
        anime: async () => {
            // 判断参数是否是0
            fibonacci.cmpl = fibonacci.getCurr() == 0
            await pc.next()
        },
        text: 'cmpl   $0x0,0x8(%ebp) 指令会把0和内存中0x8(%ebp)处的值比较，并设置相应的标志位',
    },
    {
        add: 0x80483ff,
        code: '7f 07',
        asmCode: 'jg     8048408 <f+0x14>',
        anime: async () => {
            // 如果参数不为0则跳到8048408
            if (fibonacci.cmpl === false) {
                await Promise.all([
                    pc.set(0x8048408),
                    cCodeAction.setPC(4),
                    cCodeAction.pointerAnim({
                        width: 80,
                        left: 150,
                    }),
                ])
            } else {
                await Promise.all([
                    pc.next(),
                    cCodeAction.pointerAnim({
                        width: 150,
                        left: 230,
                    }),
                ])
            }
        },
        text: 'jg     8048408 <f+0x14> 大于跳转指令，大于时跳转到8048408，小于等于时执行下一条',
    },
    {
        add: 0x8048401,
        code: 'b8 00 00 00 00',
        asmCode: 'mov    $0x0,%eax',
        anime: async () => {
            regs.eax = 0
            await Promise.all([
                setMemoryVal('eax', regs.eax),
                pc.next(),
                cCodeAction.pointerAnim({
                    width: 150,
                    left: 230,
                }),
            ])
        },
        text: 'mov    $0x0,%eax 指令将数字 0 放到寄存器eax中',
    },
    {
        add: 0x8048406,
        code: 'eb 33',
        asmCode: 'jmp    804843b <f+0x47>',
        anime: async () => {
            await Promise.all([
                pc.set(0x804843b),
                // cCodeAction.setPC(3),
                // cCodeAction.pointerAnim({
                //     width: 100,
                //     left: 300,
                // }),
            ])
        },
        text: 'jmp    804843b <f+0x47> 将跳转到804843b',
    },
    {
        add: 0x8048408,
        code: '83 7d 08 01',
        asmCode: 'cmpl   $0x1,0x8(%ebp)',
        anime: async () => {
            // 判断参数是否是1
            fibonacci.cmpl = fibonacci.getCurr() == 1
            await pc.next()
        },
        text: 'cmpl   $0x1,0x8(%ebp) 把0x1跟内存中0x8(%ebp)处的值比较，并设置相应的标志位',
    },
    {
        add: 0x804840c,
        code: '74 06',
        asmCode: 'je     8048414 <f+0x20>',
        anime: async () => {
            // 如果参数是1则跳到8048414
            if (fibonacci.cmpl === true) {
                await Promise.all([
                    pc.set(0x8048414),
                    cCodeAction.pointerAnim({
                        left: 360,
                        width: 110,
                    }),
                ])
            } else {
                await Promise.all([
                    pc.next(),
                    cCodeAction.pointerAnim({
                        width: 75,
                        left: 260,
                    }),
                ])
            }
        },
        text: 'je     8048414 <f+0x20> 相等跳转指令，相等时跳转到0x8048414，否则执行下一条',
    },
    {
        add: 0x804840e,
        code: '83 7d 08 02',
        asmCode: 'cmpl   $0x2,0x8(%ebp)',
        anime: async () => {
            // 判断参数是否是2
            fibonacci.cmpl = fibonacci.getCurr() == 2
            await pc.next()
        },
        text: 'cmpl   $0x1,0x8(%ebp) 把0x8(%ebp)跟0x2比较，并设置相应的标志位',
    },
    {
        add: 0x8048412,
        code: '75 07',
        asmCode: 'jne    804841b <f+0x27>',
        anime: async () => {
            // 如果参数不是2则跳到804841b
            if (fibonacci.cmpl === false) {
                await Promise.all([
                    pc.set(0x804841b),
                    // cCodeAction.setPC(5),
                    cCodeAction.pointerAnim({
                        top: 5 * 25,
                        width: 90,
                        left: 190,
                    }),
                ])
            } else {
                await Promise.all([
                    pc.next(),
                    cCodeAction.pointerAnim({
                        width: 110,
                        left: 350,
                    }),
                ])
            }
        },
        text: 'jne    804841b <f+0x27> 不相等跳转指令，不相等时跳转到0x804841b，否则执行下一条',
    },
    {
        add: 0x8048414,
        code: 'b8 01 00 00 00',
        asmCode: 'mov    $0x1,%eax',
        anime: async () => {
            // 如果参数不是2则跳到804841b
            regs.eax = 1
            await Promise.all([setMemoryVal('eax', regs.eax), pc.next()])
        },
        text: 'mov    $0x0,%eax 指令将数字 0 放到寄存器eax中',
    },
    {
        add: 0x8048419,
        code: 'eb 20',
        asmCode: 'jmp    804843b <f+0x47>',
        anime: async () => {
            await pc.set(0x804843b)
        },
        text: 'jmp    804843b <f+0x47> 将跳转到804843b',
    },
    {
        add: 0x804841b,
        code: '8b 45 08',
        asmCode: 'mov    0x8(%ebp),%eax',
        anime: async () => {
            // 把函数参数赋值到eax
            regs.eax = fibonacci.getCurr()
            await Promise.all([setMemoryVal('eax', regs.eax), pc.next()])
        },
        text: 'mov    0x8(%ebp),%eax 指令将内存中0x8(%ebp)处的值放到寄存器eax中',
    },
    {
        add: 0x804841e,
        code: '83 e8 01',
        asmCode: 'sub    $0x1,%eax',
        anime: async () => {
            // n-1
            regs.eax -= 1
            await Promise.all([setMemoryVal('eax', regs.eax), pc.next()])
        },
        text: 'sub    $0x1,%eax 会把寄存器eax的值减 1 后的结果放到寄存器eax中',
    },
    {
        add: 0x8048421,
        code: '89 04 24',
        asmCode: 'mov    %eax,(%esp)',
        anime: async () => {
            // 把eax的值放到esp指向的内存中，函数参数入栈
            fibonacci.push(regs.eax)
            await Promise.all([setMemoryVal(regs.esp, regs.eax), pc.next()])
        },
        text: 'mov    %eax,(%esp) 指令会把寄存器eax的值放到内存中(%esp)的位置',
    },
    {
        add: 0x8048424,
        code: 'e8 cb ff ff ff',
        asmCode: 'call   80483f4 <f>',
        anime: async () => {
            treeAction.showNode(fibonacci.paramStack.join('-'))
            // 把下一行的值入栈，pc跳到指定的位置
            await Promise.all([
                pushStack(0x8048429),
                pc.set(0x80483f4),
                cCodeAction.setPC(2),
                cCodeAction.pointerAnim({
                    width: 200,
                    left: 50,
                }),
            ])
        },
        text: 'call   80483f4 <f> 指令会把下一条要执行的地址压入栈中，然后跳转到0x80483f4的位置',
    },
    {
        add: 0x8048429,
        code: '89 c3',
        asmCode: 'mov    %eax,%ebx',
        anime: async () => {
            // 把函数参数赋值到eax
            regs.ebx = regs.eax
            await Promise.all([setMemoryVal('ebx', regs.ebx), pc.next()])
        },
        text: 'mov    %eax,%ebx 会把寄存器eax的值放到寄存器ebx中',
    },
    {
        add: 0x804842b,
        code: '8b 45 08',
        asmCode: 'mov    0x8(%ebp),%eax',
        anime: async () => {
            // 把函数参数赋值到eax
            regs.eax = fibonacci.getCurr()
            await Promise.all([setMemoryVal('eax', regs.eax), pc.next()])
        },
        text: 'mov    %eax,%ebx 会把内存中0x8(%ebp)处的值放到寄存器ebx中',
    },
    {
        add: 0x804842e,
        code: '83 e8 02',
        asmCode: 'sub    $0x2,%eax',
        anime: async () => {
            // n-2
            regs.eax -= 2
            await Promise.all([setMemoryVal('eax', regs.eax), pc.next()])
        },
        text: 'sub    $0x2,%eax 会把寄存器eax的值减 2 后的结果放到寄存器eax中',
    },
    {
        add: 0x8048431,
        code: '89 04 24',
        asmCode: 'mov    %eax,(%esp)',
        anime: async () => {
            // 把eax的值放到esp指向的内存中，函数参数入栈
            fibonacci.push(regs.eax)
            await Promise.all([setMemoryVal(regs.esp, regs.eax), pc.next()])
        },
        text: 'mov    %eax,(%esp) 指令会把寄存器eax的值放到内存中(%esp)处',
    },
    {
        add: 0x8048434,
        code: 'e8 bb ff ff ff',
        asmCode: 'call   80483f4 <f>',
        anime: async () => {
            treeAction.showNode(fibonacci.paramStack.join('-'))
            // 把下一行的值入栈，pc跳到指定的位置
            await Promise.all([
                pushStack(0x8048439),
                pc.set(0x80483f4),
                cCodeAction.setPC(2),
                cCodeAction.pointerAnim({
                    width: 200,
                    left: 50,
                }),
            ])
        },
        text: 'call   80483f4 <f> 指令会把下一条要执行的地址压入栈中，然后跳转到0x80483f4的位置',
    },
    {
        add: 0x8048439,
        code: '01 d8',
        asmCode: 'add    %ebx,%eax',
        anime: async () => {
            // 相加
            regs.eax += regs.ebx
            await Promise.all([setMemoryVal('eax', regs.eax), pc.next()])
        },
        text: 'add    %ebx,%eax 指令会把寄存器eax和寄存器ebx的值相加，然后把结果放到寄存器eax中',
    },
    {
        add: 0x804843b,
        code: '83 c4 14',
        asmCode: 'add    $0x14,%esp',
        anime: async () => {
            await Promise.all([popStackSpace(5), pc.next()])
        },
        text: 'add    $0x14,%esp 栈中20个字节的内容会出栈',
    },
    {
        add: 0x804843e,
        code: '5b',
        asmCode: 'pop    %ebx',
        anime: async () => {
            regs.ebx = stack.at(-1)[1]
            await Promise.all([
                setMemoryVal('ebx', regs.ebx),
                popStack(),
                pc.next(),
            ])
        },
        text: 'pop    %ebx 弹出栈顶的内容，并放到寄存器ebx中',
    },
    {
        add: 0x804843f,
        code: '5d',
        asmCode: 'pop    %ebp',
        anime: async () => {
            regs.ebp = stack.at(-1)[1]
            await Promise.all([
                setMemoryVal('ebp', regs.ebp),
                popStack(),
                movePointer('ebp', regs.ebp),
                pc.next(),
            ])
        },
        text: 'pop    %ebp 弹出栈顶的内容，并放到寄存器ebp中',
    },
    {
        add: 0x8048440,
        code: 'c3',
        asmCode: 'ret',
        anime: async () => {
            treeAction.setText(fibonacci.paramStack.join('-'), regs.eax)
            fibonacci.paramStack.splice(fibonacci.paramStack.length - 1, 1)
            // 移动ebp，移动pc，出栈
            let pops = stack.at(-1)[1]
            await pc.set(pops)
            let pros = [setMemoryVal('ebp', regs.ebp), popStack()]
            if (pops == 0x8048456) {
                pros.push(cCodeAction.setPC(9))
            } else if (pops == 0x8048429) {
                pros.push(
                    cCodeAction.pointerAnim({
                        top: 5 * 25,
                        left: 300,
                        width: 110,
                    })
                )
            } else {
                pros.push(cCodeAction.setPC(5))
            }
            await Promise.all(pros)
        },
        text: 'ret 指令会弹出栈顶的内容，并跳转到 从栈中被弹出来的地址 处',
    },
    {
        add: '<main>',
        code: '',
        asmCode: '',
    },
    {
        add: 0x804844a,
        code: 'c7 04 24 05 00 00 00',
        asmCode: `movl   $${getHex(globalConfig.an)},(%esp)`,
        anime: async () => {
            fibonacci.push(fibonacci.initial)
            // regs.esp = fibonacci.getCurr()

            await Promise.all([
                pc.next(),
                setMemoryVal(regs.esp, fibonacci.getCurr()),
                cCodeAction.setPC(8),
                cCodeAction.pointerAnim({
                    width: 50,
                    left: 200,
                }),
            ])
        },
        text: `movl   $${getHex(globalConfig.an)},(%esp) 指令会把数字${getHex(
            globalConfig.an
        )}放到内存中(%esp)处`,
    },
    {
        add: 0x8048451,
        code: 'e8 9e ff ff ff',
        asmCode: 'call   80483f4 <f>',
        anime: async () => {
            // 把下一行的值入栈，pc跳到指定的位置
            treeAction.showNode(fibonacci.paramStack.join('-'))
            await Promise.all([
                pushStack(0x8048456),
                pc.set(0x80483f4),
                cCodeAction.setPC(2),
                cCodeAction.pointerAnim({
                    width: 200,
                    left: 50,
                }),
            ])
        },
        text: 'call   80483f4 <f> 指令会把下一条要执行的地址压入栈中，然后跳转到0x80483f4的位置',
    },
    {
        add: 0x8048456,
        code: '89 44 24 1c',
        asmCode: 'mov    %eax,0x1c(%esp)',
        anime: async () => {
            stack.at(-4)[1] = regs.eax
            await Promise.all([
                setMemoryVal(regs.esp + 12, regs.eax),
                pc.next(),
            ])
        },
        text: 'mov    %eax,0x1c(%esp) 指令会把寄存器eax的值放到内存中0x1c(%esp)处',
    },
    {
        add: 0x804845a,
        code: 'b8 50 85 04 08',
        asmCode: 'mov    $0x8048550,%eax',
        anime: async () => {
            regs.eax = '"%d\\\\n"'
            await Promise.all([setMemoryVal('eax', regs.eax), pc.next()])
        },
        text: 'mov    $0x8048550,%eax 指令会把内存中0x8048550处的值放到寄存器eax中',
    },
    {
        add: 0x804845f,
        code: '8b 54 24 1c',
        asmCode: 'mov    0x1c(%esp),%edx',
        anime: async () => {
            regs.edx = stack.at(-4)[1]
            await Promise.all([setMemoryVal('edx', regs.edx), pc.next()])
        },
        text: 'mov    0x1c(%esp),%edx 指令会把内存中0x1c(%esp)处的值放到寄存器edx中',
    },
    {
        add: 0x8048463,
        code: '89 54 24 04',
        asmCode: 'mov    %edx,0x4(%esp)',
        anime: async () => {
            stack.at(-2)[1] = regs.edx
            await Promise.all([setMemoryVal(regs.esp + 4, regs.edx), pc.next()])
        },
        text: 'mov    %edx,0x4(%esp) 指令会把寄存器edx的值放到内存中0x4(%esp)的值',
    },
    {
        add: 0x8048467,
        code: '89 04 24',
        asmCode: 'mov    %eax,(%esp)',
        anime: async () => {
            stack.at(-1)[1] = regs.eax
            await Promise.all([setMemoryVal(regs.esp, regs.eax), pc.next()])
        },
        text: 'mov    %eax,(%esp) 指令会把寄存器eax的值放到内存中(%esp)处',
    },
    {
        add: 0x804846a,
        code: 'e8 91 fe ff ff',
        asmCode: 'call   8048300 <printf@plt>',
        anime: async () => {
            setOutput('输出结果：' + stack.at(-2)[1])
            await Promise.all([pc.next(), cCodeAction.setPC(10)])
        },
        text: 'call   8048300 <printf@plt> 调用printf函数',
    },
    {
        add: 0x804846f,
        code: 'b8 00 00 00 00',
        asmCode: 'mov    $0x0,%eax',
        anime: async () => {
            regs.eax = 0
            await Promise.all([pc.next(), setMemoryVal('eax', regs.eax)])
        },
        text: 'mov    $0x0,%eax 指令会数字0放到寄存器eax中',
    },
    {
        add: 0x8048474,
        code: 'c9',
        asmCode: 'leave',
        anime: async () => {
            await pc.next()
        },
        text: 'leave 关闭栈帧的指令（表示函数要结束了）',
    },
    {
        add: 0x8048475,
        code: 'c3',
        asmCode: 'ret',
        anime: async () => {
            await pc.next()
        },
        text: 'ret 程序将结束运行',
    },
    {
        add: 0x8048476,
        code: '90',
        asmCode: 'nop',
        anime: async () => {
            console.log('done')
            globalConfig.done = true
            setInfo('运行结束')
            await anime({
                targets: '.info',
                backgroundColor: 'rgba(254, 217, 152, 1)',
                duration: 500,
                direction: 'alternate',
                easing: 'easeOutCubic',
            }).finished
            $('.btn-reload').classList.remove('hide')
            return 'done'
        },
        text: '运行结束',
    },
]

/**
 * 栈，对应页面中的内容
 */
let stack = []

/**
 * 初始化stack
 */
function initStack() {
    stack.push(
        [regs.ebp, '0x00000000'],
        ['···', '·······'],
        [regs.esp + 12, ''],
        [regs.esp + 8, ''],
        [regs.esp + 4, ''],
        [regs.esp, '']
    )
}
initStack()

/**
 * 移动页面中的某个指针
 * @param {string} name 指针名称
 * @param {number} target 要移动到哪一个内存条
 */
async function movePointer(name, target) {
    let lineHeight = 25
    if (typeof target == 'number') {
        target = getHex(target)
    }

    let line = +$(`.memory-${target}`).dataset.i
    let y = lineHeight * line
    if (globalConfig.minimal && (name == 'pc' || name == 'esp')) {
        let asmDom = $('.asm')
        let stackDom = $('.stack')
        stackDom.scrollTop = stackDom.scrollHeight
        let asmTop = 0
        if (y > 500) {
            asmTop = 600
        } else if (y > 350) {
            asmTop = 370
        }

        if (name === 'pc' && asmDom.scrollTop !== asmTop) {
            await anime({
                targets: asmDom,
                scrollTop: asmTop,
                duration: 500 * globalConfig.speed,
                easing: 'linear',
            })
        }

        // console.log($('.asm').scrollTop)
    }

    await anime({
        targets: `.pointer.${name}`,
        translateY: y,
        easing: 'easeOutCubic',
        duration: 1000 * globalConfig.speed,
    }).finished
}

/**
 * 更改页面中内存项的值
 * @param {string | number} add 内存地址
 * @param {number} val 新值
 */
function setMemoryVal(add, val) {
    if (typeof add == 'number') {
        add = getHex(add)
    }
    let className = `.memory-${add} .val`

    return shiningMemory(add, () => {
        $(className).textContent = getHex(val)
    })
}

/**
 * 让某个内存条闪烁一下，然后执行cb
 */
function shiningMemory(add, cb = () => {}) {
    if (typeof add == 'number') {
        add = getHex(add)
    }
    let className = `.memory-${add} .val`
    return anime({
        targets: className,
        backgroundColor: 'rgba(254, 217, 152, 1)',
        duration: 500 * globalConfig.speed,
        direction: 'alternate',
        easing: 'easeOutCubic',
        complete() {
            cb()
            $(className).style.backgroundColor = '#f9f9f9'
        },
    }).finished
}

/**
 * 入栈
 */
async function pushStack(val) {
    // 更新数组
    let last = stack[stack.length - 1][0] - 4
    stack.push([last, val])
    // 更新页面
    let dom = getMemoryItem(last, val)
    dom.dataset.i = stack.length - 1
    dom.style.opacity = 0
    dom.style.height = 0
    $('.stack .memory').appendChild(dom)

    await Promise.all([
        anime({
            targets: dom,
            height: 25,
            opacity: 1,
            duration: 500 * globalConfig.speed,
            easing: 'easeOutCubic',
        }).finished,
        movePointer('esp', last),
        setMemoryVal('esp', last),
    ])

    regs.esp = last
}

/**
 * 扩展num个栈空间
 */
async function getStackSpace(num) {
    let last = stack[stack.length - 1][0] - 4
    let frag = document.createDocumentFragment()
    let selectors = []
    for (let i = 0; i < num; i++) {
        stack.push([last, ''])

        let dom = getMemoryItem(last, '')
        dom.dataset.i = stack.length - 1
        dom.style.opacity = 0

        selectors.push(`.memory-${getHex(last)}`)
        frag.appendChild(dom)

        last -= 4
    }
    $('.stack .memory').appendChild(frag)
    regs.esp = last + 4
    await Promise.all([
        anime({
            targets: selectors,
            opacity: 1,
            duration: 500 * globalConfig.speed,
            easing: 'easeOutCubic',
        }).finished,
        movePointer('esp', regs.esp),
        setMemoryVal('esp', regs.esp),
    ])
}

/**
 * 出栈
 */
async function popStack() {
    await popStackSpace(1)
}

/**
 * 出num个字节的栈
 */
async function popStackSpace(num) {
    // 删数组
    let deleted = stack.splice(stack.length - num, num)
    // 删dom
    for (const it of deleted) {
        let node = $(`.memory-${getHex(it[0])}`)
        if (node.length === undefined) {
            node.remove()
        }
    }
    // 移动esp
    regs.esp += 4 * num
    await Promise.all([
        movePointer('esp', regs.esp),
        setMemoryVal('esp', regs.esp),
    ])
}
